package com.sanbi.shield.client.core.aop;

import com.alibaba.com.caucho.hessian.io.Hessian2Input;
import com.alibaba.com.caucho.hessian.io.SerializerFactory;
import com.sanbi.shield.client.core.bean.IdempotentRetryItem;
import com.sanbi.shield.client.core.exceptions.CronFormatException;
import com.sanbi.shield.client.core.exceptions.IdempotentException;
import com.sanbi.shield.client.core.idempotent.ClientIdempotentHandlerCache;
import com.sanbi.shield.client.core.interfaces.ClientIdempotentManager;
import lombok.extern.slf4j.Slf4j;
import org.springframework.lang.NonNull;
import org.springframework.scheduling.annotation.SchedulingConfigurer;
import org.springframework.scheduling.config.ScheduledTaskRegistrar;
import org.springframework.scheduling.support.CronExpression;
import org.springframework.scheduling.support.CronTrigger;

import java.io.ByteArrayInputStream;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Base64;
import java.util.List;

/**
 * 幂等重试任务
 *
 * @author XiaoQingqing
 * @since 2024/08/22
 */
@Slf4j
public class IdempotentRetryJob implements SchedulingConfigurer {
    /**
     * Client幂等管理器
     */
    private final ClientIdempotentManager manager;
    /**
     * 是否执行中
     */
    private volatile boolean executing = false;

    /**
     * 构造器
     *
     * @param manager Client幂等管理器
     */
    public IdempotentRetryJob(ClientIdempotentManager manager){
        this.manager = manager;
    }

    @Override
    public void configureTasks(@NonNull ScheduledTaskRegistrar taskRegistrar) {
        String cron = manager.getRetryScheduleCron();
        if(!CronExpression.isValidExpression(cron)){
            throw new CronFormatException("幂等重试任务cron表达式["+ cron +"]无效");
        }
        taskRegistrar.addTriggerTask(this::retry,
            context -> new CronTrigger(cron).nextExecutionTime(context)
        );
    }

    /**
     * 重试
     */
    private void retry(){
        try {
            if(executing){
                return;
            }
            executing = true;
            List<IdempotentRetryItem> items;
            do {
                items = manager.pullExecutable();
                if(null == items){
                    return;
                }
                for (IdempotentRetryItem item : items) {
                    Method method = ClientIdempotentHandlerCache.getMethod(item.getMethodKey());
                    if(null == method){
                        manager.fail(item.getMethodKey(), item.getKey(), item.getScope(), item.getExecutedTimes(), new IdempotentException("幂等重试执行方法不存在"));
                        return;
                    }
                    try {
                        String requestInfoStr = item.getRequestInfo();
                        byte[] requestInfoBytes = Base64.getDecoder().decode(requestInfoStr);
                        ClientIdempotentAspect.RequestInfo requestInfo;
                        try(ByteArrayInputStream bis = new ByteArrayInputStream(requestInfoBytes)) {
                            SerializerFactory factory = SerializerFactory.createDefault();
                            factory.setAllowNonSerializable(true);
                            Hessian2Input hessian2Input = new Hessian2Input(bis);
                            hessian2Input.setSerializerFactory(factory);
                            requestInfo = (ClientIdempotentAspect.RequestInfo)hessian2Input.readObject();
                        }
                        method.invoke(ClientIdempotentHandlerCache.getObject(item.getMethodKey()), requestInfo.getArgs());
                        manager.success(item.getMethodKey(), item.getKey(), item.getScope());
                    } catch (InvocationTargetException e) {
                        log.error("重试幂等方法发生调用异常", e);
                        Throwable cause = e.getCause();
                        manager.fail(item.getMethodKey(), item.getKey(), item.getScope(), item.getExecutedTimes(), null == cause ? e : cause);
                    } catch (Exception e) {
                        log.error("重试幂等方法发生未知异常", e);
                        manager.fail(item.getMethodKey(), item.getKey(), item.getScope(), item.getExecutedTimes(), e);
                    }
                }
            } while (!items.isEmpty());
        } finally {
            executing = false;
        }
    }
}
